就如前面說的,雲其實包含的東西有點多,這裡會盡量把裡面的算式拆解然後一個一個示範出來,而今天要來看的是 Noise 跟 FSM 的部分。
這邊為了展示 FSM,用了此網站的範例為圖例。
這邊以 Sine Wave 來看就會看到,一般的波沒任何 Noise 的話,就會是以規律的幅度上下幅動。

float amplitude = 1.; //振幅
float frequency = 1.;
y = sin(x * frequency);
// t 時間偏移
// u_time 程式開始執行後累積的時間
float t = 0.01*(-u_time*130.0);
y += sin(x*frequency*2.1 + t)*4.5;
//y += sin(x*frequency*1.72 + t*1.121)*4.0;
//y += sin(x*frequency*2.221 + t*0.437)*5.0;
//y += sin(x*frequency*3.1122+ t*4.269)*2.5;
y *= amplitude*0.06;
如果把後面幾個開始註解掉的話,就會開始看見波的形狀越來越不規律

float amplitude = 1.;
float frequency = 1.;
y = sin(x * frequency);
float t = 0.01*(-u_time*130.0);
y += sin(x*frequency*2.1 + t)*4.5;
y += sin(x*frequency*1.72 + t*1.121)*4.0;
//y += sin(x*frequency*2.221 + t*0.437)*5.0;
//y += sin(x*frequency*3.1122+ t*4.269)*2.5;
y *= amplitude*0.06;

float amplitude = 1.;
float frequency = 1.;
y = sin(x * frequency);
float t = 0.01*(-u_time*130.0);
y += sin(x*frequency*2.1 + t)*4.5;
y += sin(x*frequency*1.72 + t*1.121)*4.0;
y += sin(x*frequency*2.221 + t*0.437)*5.0;
//y += sin(x*frequency*3.1122+ t*4.269)*2.5;
y *= amplitude*0.06;

float amplitude = 1.;
float frequency = 1.;
y = sin(x * frequency);
float t = 0.01*(-u_time*130.0);
y += sin(x*frequency*2.1 + t)*4.5;
y += sin(x*frequency*1.72 + t*1.121)*4.0;
y += sin(x*frequency*2.221 + t*0.437)*5.0;
y += sin(x*frequency*3.1122+ t*4.269)*2.5;
y *= amplitude*0.06;
而這個多個不同 Noise 疊加的結果就是 Fractal Brownian Motion(FBM)。
//fbm.hlsl
cbuffer ShaderConstants : register(b0)
{
// 螢幕解析度
float2 u_resolution;
};
// 根據輸入座標產生 0 到 1 之間的偽隨機值
float Random(float2 st)
{
// 將座標轉換成雜湊結果並只保留小數部分
return frac(sin(dot(st, float2(12.989f, 78.233f)) * 43758.5453123f));
}
// 根據二維座標計算 Value Noise
float Noise(float2 st)
{
// 取得目前所在的 grid 座標
float2 i = floor(st);
// 取得目前位置在 grid 內的局部座標
float2 f = frac(st);
// 取得左下角的隨機值
float a = Random(i);
// 取得右下角的隨機值
float b = Random(i + float2(1.0f, 0.0f));
// 取得左上角的隨機值
float c = Random(i + float2(0.0f, 1.0f));
// 取得右上角的隨機值
float d = Random(i + float2(1.0f, 1.0f));
// 使用 Smoothstep 曲線平滑局部座標
float2 u = f * f * (3.0f - 2.0f * f);
// 對四個角落的隨機值進行雙線性插值
return lerp(a, b, u.x) + (c - a) * u.y * (1.0f - u.x)
+ (d - b) * u.x * u.y;
}
#define OCTAVES 6
// 疊加多層不同頻率與振幅的 Noise 產生 Fractal Brownian Motion
float Fbm(float2 st)
{
// 儲存累積的 Noise 值
float value = 0.0f;
// 設定第一層 Noise 的振幅
float amplitude = 0.5f;
// 要求 Compiler 展開迴圈以減少執行時的迴圈控制成本
[unroll]
for (int i = 0; i < OCTAVES; ++i)
{
// 將目前頻率的 Noise 乘上振幅後累加
value += amplitude * Noise(st);
// 將座標放大兩倍以提高下一層 Noise 的頻率
st *= 2.0f;
// 將下一層 Noise 的振幅降低一半
amplitude *= 0.5f;
}
// 回傳所有 octave 疊加後的結果
return value;
}
// 使用 Vertex ID 產生 Fullscreen Triangle 的頂點位置
float4 VSMain(uint vertexId : SV_VertexID) : SV_Position
{
// 根據 Vertex ID 產生三角形頂點座標
float2 position = float2((vertexId << 1) & 2, vertexId & 2);
// 將座標轉換到 NDC 範圍並翻轉 Y 軸
return float4(position * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
}
// 根據 Pixel 的螢幕座標計算最終顏色
float4 PSMain(float4 position : SV_Position) : SV_Target
{
// 將 Pixel 座標轉換成 0 到 1 的 UV 座標
float2 st = position.xy / u_resolution.xy;
// 根據螢幕 Aspect Ratio 修正 X 軸比例避免 Noise 被拉伸
st.x *= u_resolution.x / u_resolution.y;
// 放大取樣座標並計算 FBM Noise
float value = Fbm(st * 3.0f);
// 將 Noise 值輸出成灰階顏色
return float4(value, value, value, 1.0f);
}
#include <cstdlib>
#include <directx/d3dx12_core.h>
#include <exception>
#include <stdexcept>
#include "constant_buffer.h"
#include "graphics_engine.h"
#include "my_engine.h"
#include "skyline_debugger.h"
#include "system.h"
namespace
{
struct ShaderConstants
{
float resolution[2];
};
D3D12_GRAPHICS_PIPELINE_STATE_DESC createPipelineDescription(
ID3D12RootSignature* rootSignature, ID3DBlob* vertexShader, ID3DBlob* pixelShader)
{
if (rootSignature == nullptr)
throw std::invalid_argument("DayX_Fbm: Root signature is required.");
if (vertexShader == nullptr)
throw std::invalid_argument("DayX_Fbm: Vertex shader is required.");
if (pixelShader == nullptr)
throw std::invalid_argument("DayX_Fbm: Pixel shader is required.");
D3D12_GRAPHICS_PIPELINE_STATE_DESC description{};
description.InputLayout = {nullptr, 0};
description.pRootSignature = rootSignature;
description.VS = CD3DX12_SHADER_BYTECODE(vertexShader);
description.PS = CD3DX12_SHADER_BYTECODE(pixelShader);
description.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
description.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
description.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
description.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
description.DepthStencilState.DepthEnable = FALSE;
description.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
description.DepthStencilState.StencilEnable = FALSE;
description.SampleMask = UINT_MAX;
description.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
description.NumRenderTargets = 1;
description.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
description.DSVFormat = DXGI_FORMAT_D32_FLOAT;
description.SampleDesc.Count = 1;
return description;
}
}
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE previous, LPWSTR commandLine, int showCommand)
{
try
{
SkylineDebugger::Initialize("DayX_Fbm");
initWindow(instance, previous, commandLine, showCommand, TEXT("DayX_Fbm"));
if (g_hWnd == nullptr)
throw std::runtime_error("DayX_Fbm: Failed to create the application window.");
GraphicsEngine graphicsEngine;
graphicsEngine.init(g_hWnd, FRAME_BUFFER_W, FRAME_BUFFER_H);
SkylineDebugger::ConfigureD3D12(graphicsEngine.getD3DDevice());
RootSignature rootSignature;
rootSignature.init(D3D12_FILTER_MIN_MAG_MIP_LINEAR,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP);
Shader vertexShader, pixelShader;
vertexShader.loadVS("assets/shaders/fbm.hlsl", "VSMain");
pixelShader.loadPS("assets/shaders/fbm.hlsl", "PSMain");
PipelineState pipelineState;
pipelineState.init(createPipelineDescription(
rootSignature.get(), vertexShader.getCompiledBlob(), pixelShader.getCompiledBlob()));
ShaderConstants constants{{static_cast<float>(FRAME_BUFFER_W), static_cast<float>(FRAME_BUFFER_H)}};
ConstantBuffer constantBuffer;
constantBuffer.init(sizeof(constants), &constants);
RenderContext& renderContext = graphicsEngine.getRenderContext();
while (dispatchWindowMessage())
{
graphicsEngine.beginRender();
renderContext.setRootSignature(rootSignature);
renderContext.setPipelineState(pipelineState);
renderContext.setPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
renderContext.setGraphicsRootConstantBufferView(0, constantBuffer.getGPUVirtualAddress());
renderContext.draw(3);
graphicsEngine.endRender();
}
SkylineDebugger::Shutdown();
return EXIT_SUCCESS;
}
catch (const std::exception& error)
{
SkylineDebugger::ShowFatalError("DayX_Fbm initialization failed", error);
SkylineDebugger::Shutdown();
return EXIT_FAILURE;
}
catch (...)
{
SkylineDebugger::ShowFatalError("DayX_Fbm initialization failed", "An unknown fatal error occurred.");
SkylineDebugger::Shutdown();
return EXIT_FAILURE;
}
}

https://thebookofshaders.com/13/
https://www.shadertoy.com/view/4dS3Wd